package com.detect.service;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.detect.core.HashCode;
import com.detect.core.HashCodeImpl;
import com.detect.exception.FileDoesNotExistException;
import com.detect.model.FileInfo;
import com.detect.model.FileType;
import com.detect.model.HashLevel;

@Service("scanService")
public class ScanServiceImpl implements ScanService {

    private static final Logger logger = LoggerFactory.getLogger("ScanService");

    private List<FileInfo> fileList;

    private boolean isScanSub;

    // 文件夹名字后加"_x"来表示相对于顶层是第几层目录
    // TODO 可以考虑用TreeMap<Integer, List<String>>来持有几层目录对应的list来实现
    private List<String> topDirs;

    private Map<String, List<FileInfo>> duplicateFileWithMD5List;

    private Map<String, List<FileInfo>> duplicateFileWithNameList;

    private long minSize;

    private long maxSize;

    // the distance to root directory, it's enable whenisScanSub equals true
    private int scanDepth;

    private HashLevel hashLevel;

    /**
     * 默认构造器
     */
    public ScanServiceImpl() {
        fileList = new CopyOnWriteArrayList<FileInfo>();
        this.isScanSub = false;
        this.maxSize = ScanService.DEFAULT_MAX_SIZE;
        this.minSize = ScanService.DEFAULT_MIN_SIZE;
        this.scanDepth = 0;
        this.hashLevel = HashLevel.DEFAULT_LEVEL;
        this.duplicateFileWithMD5List = new HashMap<String, List<FileInfo>>();
        this.duplicateFileWithNameList = new HashMap<String, List<FileInfo>>();
    }

    /**
     * @param topDir
     *            顶层目录
     * @param isScanSub
     *            是否搜索子目录
     * @param minSize
     *            最小忽略文件大小
     * @param maxSize
     *            最大忽略文件大小
     * @param scanDepth
     *            搜索子文件夹深度
     */
    public ScanServiceImpl(String topDir, boolean isScanSub, long minSize,
            long maxSize, int scanDepth) {
        List<String> topDirList = new ArrayList<String>();
        topDir = topDir + "_0";
        topDirList.add(topDir);
        init(topDirList, isScanSub, minSize, maxSize, scanDepth);
    }

    /**
     * @param topDirs
     *            顶层目录集合
     * @param isScanSub
     *            是否搜索子目录
     * @param minSize
     *            最小忽略文件大小
     * @param maxSize
     *            最大忽略文件大小
     * @param scanDepth
     *            搜索子文件夹深度
     */
    public ScanServiceImpl(String[] topDirs, boolean isScanSub, long minSize,
            long maxSize, int scanDepth) {
        List<String> topDirlist = new ArrayList<String>();
        for (String topDir: topDirs) {
            topDir = topDir + "_0";
            topDirlist.add(topDir);
        }
        init(topDirlist, isScanSub, minSize, maxSize, scanDepth);
    }

    private void init(List<String> topDirs, boolean isScanSub, long minSize,
            long maxSize, int scanDepth) {
        fileList = new CopyOnWriteArrayList<FileInfo>();
        this.isScanSub = isScanSub;
        this.topDirs = topDirs;
        this.maxSize = maxSize;
        this.minSize = minSize;
        this.scanDepth = scanDepth;
        this.hashLevel = HashLevel.DEFAULT_LEVEL;
        this.duplicateFileWithMD5List = new HashMap<String, List<FileInfo>>();
        this.duplicateFileWithNameList = new HashMap<String, List<FileInfo>>();
    }

    public boolean isScanSub() {
        return isScanSub;
    }

    public void setScanSub(boolean isScanSub) {
        this.isScanSub = isScanSub;
    }

    public void setTopDirs(List<String> topDirs) {
        List<String> topDirlist = new ArrayList<String>();
        for (String topDir: topDirs) {
            topDir = topDir + "_0";
            topDirlist.add(topDir);
        }
        this.topDirs = topDirlist;
    }

    public long getMinSize() {
        return minSize;
    }

    public void setMinSize(long minSize) {
        this.minSize = minSize;
    }

    public long getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(long maxSize) {
        this.maxSize = maxSize;
    }

    public int getScanDepth() {
        return scanDepth;
    }

    public void setScanDepth(int scanDepth) {
        this.scanDepth = scanDepth;
    }

    public Map<String, List<FileInfo>> getDuplicateFileWithMD5List() {
        return duplicateFileWithMD5List;
    }

    public Map<String, List<FileInfo>> getDuplicateFileWithNameList() {
        return duplicateFileWithNameList;
    }

    public void setHashLevel(HashLevel hashLevel) {
        this.hashLevel = hashLevel;
    }

    @Override
    public void doScan() throws FileDoesNotExistException {
        scannFiles();
        if (!fileList.isEmpty())
            setHashCode();
    }

    private void scannFiles() throws FileDoesNotExistException {
        int dirs = 0;
        for (int i = 0; i < topDirs.size();) {
            String dirName = topDirs.get(i);
            int currLevel = Integer.parseInt(dirName.substring(dirName
                    .lastIndexOf('_') + 1));
            dirName = dirName.substring(0, dirName.lastIndexOf('_'));
            File topFile = new File(dirName);
            if (!topFile.exists())
                throw new FileDoesNotExistException(topDirs.get(i));
            logger.info("scan dictionary :" + topDirs.get(i));
            dirs++;
            File[] subFiles = topFile.listFiles();
            for (File tFile: subFiles) {
                if (tFile.isDirectory() && isScanSub && currLevel < scanDepth) {
                    logger.info("add directory " + tFile.getName());
                    topDirs.add(tFile.getAbsolutePath() + "_" + (++currLevel));
                } else {
                    long size = tFile.length();
                    if (tFile.isFile() && (size > minSize || minSize == -1)
                            && (size < maxSize || maxSize == -1)) {
                        FileInfo tFileInfo = new FileInfo();
                        String fileName = tFile.getName();
                        try {
                            tFileInfo.setFileType(FileType.valueOf(fileName
                                    .substring(fileName.lastIndexOf('.') + 1)
                                    .toUpperCase()));
                        } catch (IllegalArgumentException ex) {
                            tFileInfo.setFileType(FileType.OTHER);
                        }
                        if (tFileInfo.getFileType() != FileType.NONE) {
                            int lastOfQout = fileName.lastIndexOf('.');
                            tFileInfo.setFileName(lastOfQout == -1 ? fileName
                                    : fileName.substring(0, lastOfQout));
                            tFileInfo.setFilePath(tFile.getAbsolutePath());
                            tFileInfo.setFileSize(size);
                            fileList.add(tFileInfo);
                        }
                    }
                }
            }
            topDirs.remove(i);
        }
        logger.info("total dirs = " + dirs);
    }

    private void setHashCode() {
        // TODO 这里可以开多线程计算hash值
        HashSet<FileInfo> fileSet = new HashSet<FileInfo>();// 筛选同hash值(相同文件)文件
        HashSet<String> fileNamesSet = new HashSet<String>();// 筛选同名文件
        HashCode hashCode = new HashCodeImpl(hashLevel);
        if (!fileList.isEmpty()) {
            for (FileInfo fileInfo: fileList) {
                String md5String = hashCode.getFileMD5(new File(fileInfo
                        .getFilePath()));
                fileInfo.setMd5(md5String);
                // 检测同hash值文件
                if (!fileSet.add(fileInfo)) {
                    List<FileInfo> tList = duplicateFileWithMD5List
                            .get(md5String);
                    tList.add(fileInfo);
                    duplicateFileWithMD5List.put(md5String, tList);
                } else {
                    List<FileInfo> tList = new ArrayList<FileInfo>();
                    tList.add(fileInfo);
                    duplicateFileWithMD5List.put(md5String, tList);
                }
                // 检测同名文件
                if (!fileNamesSet.add(fileInfo.getFileName())) {
                    List<FileInfo> tList = duplicateFileWithNameList
                            .get(fileInfo.getFileName());
                    tList.add(fileInfo);
                    duplicateFileWithNameList.put(md5String, tList);
                } else {
                    List<FileInfo> tList = new ArrayList<FileInfo>();
                    tList.add(fileInfo);
                    duplicateFileWithNameList
                            .put(fileInfo.getFileName(), tList);
                }
            }
        }
    }

    @Override
    public int getFileCount() {
        // TODO Auto-generated method stub
        return fileList.size();
    }

    @Override
    public List<FileInfo> getFileInfos() {
        return fileList;
    }

    @Override
    public Map<String, List<FileInfo>> getDuplicateFileWithMD5() {
        return duplicateFileWithMD5List;
    }

    @Override
    public Map<String, List<FileInfo>> getDuplicateFileWithName() {
        return duplicateFileWithNameList;
    }

}
